#!/usr/bin/env python3
"""
Sanity-check task catalogues emitted in projects/*/includes/tasks.mk.

Checks performed:
- Sentinel tasks exist with expected statuses.
- No duplicate task IDs within a catalogue.
- Dependency registrations reference defined tasks and prerequisites.
"""

from __future__ import annotations

import re
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]

TASK_PATTERN = re.compile(
    r"\$\(\s*call\s+REGISTER_TASK\s*,\s*([^,]+)\s*,\s*([^,]+)\s*,\s*([^,]+)\s*,\s*([^,]+)\s*,",
    re.IGNORECASE,
)
DEP_PATTERN = re.compile(
    r"\$\(\s*call\s+REGISTER_DEP\s*,\s*([^,]+)\s*,\s*([^)]+?)\s*\)",
    re.IGNORECASE,
)

EXPECTED: dict[str, dict[str, str]] = {
    "projects/flexinvert/includes/tasks.mk": {
        "FLEX_A1": "done",
        "FLEX_P51": "todo",
        "FLEX_C5": "todo",
    },
    "projects/gsi/includes/tasks.mk": {
        "GSI_SP2_CRTM": "wip",
        "GSI_SP3_BUFR": "done",
        "GSI_SP5_BACKEND_AUTO": "todo",
    },
    "projects/wrf/includes/tasks.mk": {
        "WRF_FOUND_A1": "todo",
        "WRF_MCV_PHASEB1": "done",
        "WRF_SEM_NEW_C1": "todo",
    },
}


def parse_catalogue(path: Path) -> tuple[dict[str, str], list[tuple[str, str]]]:
    tasks: dict[str, str] = {}
    deps: list[tuple[str, str]] = []
    for line in path.read_text().splitlines():
        task_match = TASK_PATTERN.search(line)
        if task_match:
            task_id, status = task_match.group(1).strip(), task_match.group(2).strip()
            if task_id in tasks:
                raise ValueError(f"{path}: duplicate task id {task_id}")
            tasks[task_id] = status
        dep_match = DEP_PATTERN.search(line)
        if dep_match:
            target = dep_match.group(1).strip()
            prerequisites = [p.strip() for p in dep_match.group(2).split() if p.strip()]
            deps.extend((target, prereq) for prereq in prerequisites)
    return tasks, deps


def main() -> int:
    failures: list[str] = []
    for rel_path, expected in EXPECTED.items():
        path = ROOT / rel_path
        if not path.exists():
            failures.append(f"{rel_path}: file not found")
            continue
        try:
            tasks, deps = parse_catalogue(path)
        except ValueError as exc:
            failures.append(str(exc))
            continue
        for task_id, status in expected.items():
            if task_id not in tasks:
                failures.append(f"{rel_path}: missing task {task_id}")
            elif tasks[task_id] != status:
                failures.append(
                    f"{rel_path}: task {task_id} status {tasks[task_id]!r} != expected {status!r}"
                )
        # Validate dependencies reference known tasks.
        for target, prereq in deps:
            if target not in tasks:
                failures.append(f"{rel_path}: dependency target {target} undefined")
            if prereq not in tasks:
                failures.append(f"{rel_path}: dependency prereq {prereq} undefined")
    if failures:
        sys.stderr.write("Task catalogue verification failed:\n")
        for failure in failures:
            sys.stderr.write(f"  - {failure}\n")
        return 1
    print("Task catalogue verification passed.")
    return 0


if __name__ == "__main__":
    sys.exit(main())
